package com.gogant.spider.js;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public final class PhantomPool {
    private static PhantomPool instance;

    public static void initPool(int poolSize, String executeFile, String proxyFile, String... options) {
        synchronized (PhantomPool.class) {
            if (instance == null) {
                instance = new PhantomPool(poolSize, executeFile, proxyFile);
                if (options != null && options.length > 0) {
                    for (String option : options) {
                        instance.addExecuteOption(option);
                    }
                }
            }
        }
    }

    public static PhantomPool getInstance() throws IllegalStateException{
        if (instance == null) {
            throw new IllegalStateException("phantom pool not prepared");
        }
        return instance;
    }

    private final List<String> executeOptionList = new ArrayList<String>();
    private final List<PhantomProxy> proxyPool = new ArrayList<PhantomProxy>();
    private final LinkedList<PhantomProxy> proxyQueue = new LinkedList<PhantomProxy>();
    private String executeFile = "phantomjs";
    private String proxyFile = "proxy.js";
    private String[] executeOptionArray = null;
    private int poolSize;
    private Charset defaultCharset = Charset.forName("UTF-8");
    private long defaultMessageTimeout = 60000;

    private PhantomPool(int poolSize, String executeFile, String proxyFile) {
        setPoolSize(poolSize);
        if (executeFile == null || executeFile.length() == 0) {
            throw new IllegalArgumentException("execute file can not be null");
        }
        if (proxyFile == null || proxyFile.length() == 0) {
            throw new IllegalArgumentException("proxy file can not be null");
        }
        this.executeFile = executeFile;
        this.proxyFile = proxyFile;
    }

    public int getPoolSize() {
        return poolSize;
    }

    public String getExecuteFile() {
        return executeFile;
    }

    public String getProxyFile() {
        return proxyFile;
    }

    public void addExecuteOption(String name, String value) {
        String option = "--" + name + "=" + value;
        addExecuteOption(option);
    }

    public void addExecuteOption(String option) {
        synchronized (executeOptionList) {
            if (executeOptionList.contains(option)) {
                return;
            } else {
                executeOptionList.add(option);
                executeOptionArray = null;
            }
        }
    }

    public void removeExecuteOption(String name) {
        String key = "--" + name + "=";
        synchronized (executeOptionList) {
            for (int i = executeOptionList.size() - 1; i >= 0; i--) {
                String param = executeOptionList.get(i);
                if (param.startsWith(key)) {
                    executeOptionList.remove(i);
                    executeOptionArray = null;
                }
            }
        }
    }

    public List<String> getExecuteOptionList() {
        synchronized (executeOptionList) {
            return new ArrayList<String>(executeOptionList);
        }
    }

    private String[] getExecuteOptionArray() {
        synchronized (executeOptionList) {
            if (executeOptionArray == null) {
                executeOptionArray = executeOptionList.toArray(new String[executeOptionList.size()]);
            }
            return executeOptionArray;
        }
    }

    public PhantomProxy getProxy() throws InterruptedException {
        synchronized (proxyQueue) {
            PhantomProxy proxy = null;
            while (proxy == null) {
                if (proxyQueue.size() > 0) {
                    proxy = proxyQueue.pollFirst();
                } else if (proxyPool.size() < poolSize) {
                    proxy = new PhantomProxy(executeFile, proxyFile);
                    proxyPool.add(proxy);
                } else {
                    proxyPool.wait();
                }
            }
            proxy.setCharset(defaultCharset);
            proxy.setExecuteOptions(getExecuteOptionArray());
            proxy.setMessageTimeout(defaultMessageTimeout);
            proxy.setPhantomPool(this);
            return proxy;
        }
    }

    protected void checkInProxy(PhantomProxy proxy) {
        if (proxy == null) {
            return;
        }
        synchronized (proxyQueue) {
            proxy.setPhantomPool(null);
            if (!proxyPool.contains(proxy) || proxyPool.size() > poolSize) {
                proxy.close();
                return;
            }
            proxyQueue.addFirst(proxy);
        }
    }

    public void setPoolSize(int poolSize) {
        if (poolSize <= 0) {
            throw new IllegalArgumentException("pool size must > 0");
        }
        synchronized (proxyQueue) {
            this.poolSize = poolSize;
            while (proxyPool.size() > poolSize && proxyQueue.size() > 0) {
                PhantomProxy proxy = proxyQueue.pollLast();
                proxy.close();
                proxyPool.remove(proxy);
            }
        }
    }

    public void setDefaultCharset(Charset charset) {
        if (charset == null) {
            charset = Charset.forName("UTF-8");
        }
        this.defaultCharset = charset;
    }

    public Charset getDefaultCharset() {
        return defaultCharset;
    }

    public void setDefaultMessageTimeout(long defaultMessageTimeout) {
        if (defaultMessageTimeout <= 0) {
            defaultMessageTimeout = 60000;
        }
        this.defaultMessageTimeout = defaultMessageTimeout;
    }

    public long getDefaultMessageTimeout() {
        return defaultMessageTimeout;
    }

    public void destroy() {
        synchronized (proxyQueue) {
            proxyQueue.clear();
            for (PhantomProxy proxy : proxyPool) {
                proxy.close();
            }
            proxyPool.clear();
        }
    }
}
